#include "eviction/lru.hpp"

/**
 * \file
 * \ingroup cacheEvict
 * cas::CacheEvictionLru implementation.
 */

namespace cas
{
    SLOG_COMPONENT_DEFINE("CacheEvictionLru");

    CacheEvictionLru::CacheEvictionLru(uint32_t cacheSize)
    {
        SLOG_FUNCTION(cacheSize);
        SASSERT(cacheSize > 0);
        pool.set_eviction(1);
        pool.resize(cacheSize);
    }

    CacheEvictionLru::~CacheEvictionLru()
    {
        reset();
    }

    uint16_t CacheEvictionLru::lookup(const CacheRequest &request)
    {
        SLOG_FUNCTION(static_cast<int32_t>(request.type) << request.page.devno << request.page.lba << request.size);
        // record
        switch (request.type)
        {
        case CacheRequest::CacheRequestLookup:
            stat.requestNumber++;
            stat.requestSize += request.size; // record request lba size
            break;
        case CacheRequest::CacheRequestPrefetch:
            stat.predictedDataSize += request.size; // record prefetched block
            break;
        case CacheRequest::CacheRequestTypeLength:
            SFATAL_ERROR_CONT("CacheRequest type not set");
        default:
            break;
        }

        // process of each block
        CacheRequest tmpReq = request;
        uint16_t hitNum = 0;

        for (size_t idx = 0; idx < request.size; idx++)
        {
            // auto it = listMap.find(tmpReq.page);
            auto it = pool.find(tmpReq.page);
            if (it != pool.map_end())
            {
                // cache item found
                auto p = pool.get(it->second);
                SABORT_MSG_IF(*p != tmpReq.page, "LRU found error" << p->devno << p->lba << " " << tmpReq.page.lba);

                // update(it->second); // update cache
                pool.move_back(tmpReq.page);

                if (request.type == CacheRequest::CacheRequestLookup)
                {
                    hitNum++; // record hit number
                    if (p->isPrefetch)
                    {
                        stat.effectivePrefetchSize++;
                        // p->isPrefetch = false; // \note have fun
                        p->isPrefetchHit = true;
                    }
                }
            }
            else
            {
                push(tmpReq); // cache not found
            }

            tmpReq.page.lba++; // next block
        }

        if (request.type == CacheRequest::CacheRequestLookup)
        {
            // record hit info
            if (hitNum > 0)
            {
                stat.requestHitNumber++;
            }
            stat.requestHitSize += hitNum;
        }

        return hitNum;
    }

    void CacheEvictionLru::reset()
    {
        pool.clear();
    }

    void CacheEvictionLru::setCacheSize(uint32_t cacheSize)
    {
        pool.clear();
        pool.resize(cacheSize);
    }

    uint32_t CacheEvictionLru::getCacheSize() const
    {
        return pool.capacity();
    }

    void CacheEvictionLru::push(CacheRequest request)
    {
        LruCachePage p(request.page.devno, request.page.lba);
        p.isPrefetch = CacheRequest::CacheRequestPrefetch == request.type;

        if (pool.capacity() == pool.size())
        {
            // full, evict least recent
            auto evictPage = pool.pop_front().second;
            stat.wastedDataSize++;
            if (evictPage.isPrefetch && !evictPage.isPrefetchHit)
            {
                stat.uselessPrefetchSize++;
            }
        }
        SASSERT(false == pool.push_back(request.page, p)); // should not evict
    }

    void CacheEvictionLru::print() const
    {
        const auto &s = stat;
        printf("[lru] %ld, hit %lf (%ld / %ld), eff %lf (%ld | %ld)\n",
               s.requestNumber, s.getHitRatio(), s.requestHitSize, s.requestSize,
               s.getPrefetchPrecision(), s.effectivePrefetchSize, s.uselessPrefetchSize);
    }

    // LruCachePage
    CachePage CacheEvictionLru::LruCachePage::toCachePage() const
    {
        return CachePage(devno, lba);
    }

} // namespace cas
